package org.test4j.mock.faking.meta;

import org.test4j.mock.faking.util.ClassLoad;
import org.test4j.mock.faking.util.TypeUtility;
import org.test4j.mock.startup.Startup;

import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.atomic.AtomicLong;

import static org.test4j.mock.faking.modifier.FakeTransformer.applyFakes;

/**
 * MockUp具体实例
 *
 * @author darui.wu
 */
public abstract class AbstractFake {
    static {
        Startup.verifyInitialization();
    }

    /**
     * MockUp实例唯一编号
     */
    private static final AtomicLong FakeSequence = new AtomicLong(0L);
    /**
     * 被mock的具体raw type
     */
    protected final Class declaredToFake;
    /**
     * 指定实例列表
     */
    protected final Set<Integer> fakedHashCodes = new HashSet<>();
    /**
     * MockUp实例编号
     */
    protected final long fakedSeqNo = FakeSequence.incrementAndGet();

    protected AbstractFake(Integer[] fakedHashCodes) {
        Class clazz = this.getClass();
        this.addFakedHashCodes(fakedHashCodes);
        Type targetType = TypeUtility.getTypeToFake(clazz);
        this.declaredToFake = TypeUtility.getClassType(targetType);
        applyFake();
    }

    /**
     * 对非public类进行mock定义
     *
     * @param fullClassName
     * @param fakedHashCodes 指定mock的实例内存地址列表
     */
    protected AbstractFake(String fullClassName, Integer[] fakedHashCodes) {
        this(ClassLoad.loadClass(fullClassName), fakedHashCodes);
    }

    protected AbstractFake(Class declaredToFake, Integer[] fakedHashCodes) {
        this.declaredToFake = declaredToFake;
        this.addFakedHashCodes(fakedHashCodes);
        applyFake();
    }

    private void addFakedHashCodes(Integer[] fakedHashCodes) {
        if (fakedHashCodes == null || fakedHashCodes.length == 0) {
            return;
        }
        Arrays.stream(fakedHashCodes)
            .filter(code -> code != null)
            .forEach(this.fakedHashCodes::add);
    }

    protected AbstractFake(Class declaredToFake, Set<Integer> fakedHashCodes) {
        this.declaredToFake = declaredToFake;
        this.fakedHashCodes.addAll(fakedHashCodes);
        applyFake();
    }

    protected void applyFake() {
        applyFakes(this.declaredToFake);
    }
}